12장. 시스템도 자란다
원문 12장 "소프트웨어 설계의 프랙털 기하학"을 입문자용으로 다시 풀었다.
핵심은 "작은 함수에서 보던 결합 균형 문제가 큰 서비스와 조직에서도 반복된다"는 점이다.
12장은 제목부터 어렵게 느껴질 수 있다.
프랙털?
기하학?
소프트웨어 설계?
입문 단계에서는 이렇게 이해하면 충분하다.
소프트웨어는 커질수록 같은 종류의 결합 문제가 여러 크기에서 반복된다.
작은 함수 안에서도 결합이 생긴다.
클래스 사이에서도 결합이 생긴다.
모듈 사이에서도 결합이 생긴다.
서비스 사이에서도 결합이 생긴다.
팀 사이에서도 결합이 생긴다.
크기만 달라질 뿐, 질문은 비슷하다.
- 얼마나 강하게 연결됐나?
- 얼마나 멀리 떨어졌나?
- 얼마나 자주 바뀌나?
0. 이 장의 새 단어
| 단어 | 아주 쉬운 뜻 |
|---|---|
| 네트워크 시스템 | 많은 부품이 연결되어 전체를 이루는 시스템 |
| 프랙털 | 작은 구조와 큰 구조가 비슷한 모양을 반복하는 현상 |
| 자기 유사성 | 크기는 다른데 모양이나 문제가 비슷하게 반복되는 성질 |
| 성장 압력 | 시스템이 커질수록 생기는 복잡도 증가 |
| 추상화 | 세부사항을 감추고 중요한 약속만 드러내는 방법 |
| 책임 할당 | 어떤 부품이 어떤 일을 맡을지 정하는 설계 작업 |
1. 귀납 도입: 프로젝트가 커지면 왜 갑자기 힘들어질까?
처음에는 파일이 세 개였다.
app.py
orders.py
users.py
이때는 모든 것을 한눈에 볼 수 있다.
함수가 어디 있는지도 기억난다.
바꾸면 어디가 영향받는지도 대충 안다.
그런데 프로젝트가 커진다.
orders/
payments/
shipping/
notifications/
admin/
analytics/
이제 한 변경이 어디까지 퍼질지 모른다.
주문을 바꿨더니 정산이 깨진다.
정산을 바꿨더니 관리자 화면이 깨진다.
문제는 단순히 파일 수가 늘어난 것이 아니다.
연결 수가 늘어난 것이다.
소프트웨어는 부품들의 네트워크다.
부품이 많아질수록 가능한 연결도 빠르게 늘어난다.
2. 이 장에서 딱 5가지만
- 소프트웨어는 부품과 연결로 이루어진 네트워크다.
- 시스템이 커지면 기능보다 연결이 더 빠르게 복잡해질 수 있다.
- 같은 결합 균형 문제가 함수, 클래스, 모듈, 서비스에서 반복된다.
- 성장을 견디려면 더 좋은 추상화와 책임 배치가 필요하다.
- 프랙털 모듈성은 모든 크기에서 균형 잡힌 결합을 반복하는 것이다.
3. 소프트웨어는 네트워크다
소프트웨어를 코드 줄의 모음으로만 보면 중요한 것을 놓친다.
소프트웨어는 부품과 연결의 네트워크다.
| 소프트웨어 요소 | 네트워크 관점 |
|---|---|
| 함수 | 작은 노드 |
| 클래스 | 조금 큰 노드 |
| 모듈 | 더 큰 노드 |
| 서비스 | 독립 배포되는 큰 노드 |
| API | 노드 사이의 연결 |
| 데이터 모델 | 여러 노드가 공유하는 지식 |
문제는 노드 수보다 연결 수다.
부품이 3개면 가능한 연결은 적다.
부품이 30개면 연결 가능성은 훨씬 많다.
그래서 시스템이 커질수록 "어떤 부품을 만들까"보다 "어떻게 연결할까"가 더 중요해진다.
4. 성장은 선형이지만 복잡함은 선형이 아닐 수 있다
기능은 하나씩 늘어나는 것처럼 보인다.
- 주문 기능
- 결제 기능
- 배송 기능
- 알림 기능
- 정산 기능
하지만 기능 사이의 연결은 더 빠르게 늘 수 있다.
주문은 결제를 알아야 한다.
결제는 정산과 연결된다.
배송은 주문 상태를 알아야 한다.
알림은 주문과 결제와 배송을 모두 본다.
관리자 화면은 거의 모든 것을 본다.
이렇게 되면 기능 하나를 추가하는 비용보다 연결을 관리하는 비용이 커진다.
원서의 중요한 관점은 이것이다.
| 항목 | 성장 방식 |
|---|---|
| 기능 수 | 비교적 천천히 늘 수 있다 |
| 지식 공유 | 잘 설계하면 덜 늘 수 있다 |
| 복잡도 | 연결을 방치하면 빠르게 늘 수 있다 |
| 사람의 이해력 | 거의 고정되어 있다 |
사람의 머리는 갑자기 두 배로 커지지 않는다.
그래서 시스템이 커질수록 구조가 더 중요해진다.
5. 큰 구조는 작은 구조를 단순히 키운 것이 아니다
작은 책상을 두 배 크게 만들면 그냥 큰 책상이 될 수 있다.
하지만 큰 건물을 작은 방의 단순 확대판으로 만들 수는 없다.
소프트웨어도 비슷하다.
작은 프로그램에서 괜찮던 구조가 큰 시스템에서는 깨질 수 있다.
작은 프로그램:
static dynamic HandleOrder(dynamic order) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
charge(order); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
ship(order); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
notify(order); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
}
큰 시스템:
Order Service
Payment Service
Shipping Service
Notification Service
작을 때는 함수 호출 하나면 충분했다.
커지면 네트워크 지연, 실패, 배포 순서, 데이터 일관성까지 생각해야 한다.
규모가 커지면 모양을 바꿔야 한다.
이것이 성장 압력이다.
6. 더 단단한 재료 또는 더 좋은 모양
원서는 시스템이 커질 때 두 가지 방향을 이야기한다.
- 더 강한 재료를 쓴다.
- 더 좋은 모양을 만든다.
소프트웨어에서 더 강한 재료는 도구, 언어, 자동화, 분석 기술일 수 있다.
좋은 도구는 도움이 된다.
하지만 도구만으로 모든 복잡함이 사라지지는 않는다.
그래서 더 좋은 모양이 필요하다.
소프트웨어에서 좋은 모양은 추상화와 책임 배치다.
- 세부사항을 감춘다.
- 안정적인 계약을 둔다.
- 함께 바뀌는 것을 모은다.
- 멀리 있는 것은 약하게 연결한다.
- 자주 바뀌는 지식은 한곳에 둔다.
즉 10장과 11장의 균형 감각이 규모가 커져도 계속 필요하다.
7. 프랙털을 아주 쉽게 말하면
프랙털은 크기가 달라도 비슷한 모양이 반복되는 것을 말한다.
소프트웨어 설계에서 중요한 것은 그림이 아니다.
반복되는 질문이다.
함수 안에서:
- 이 조건문이 다른 책임을 너무 많이 아는가?
- 이 작은 로직이 자주 바뀌는가?
클래스 사이에서:
- 이 클래스가 다른 클래스 내부 구조를 너무 많이 아는가?
- 함께 바뀌는 메서드가 흩어져 있는가?
모듈 사이에서:
- 이 모듈이 다른 모듈의 모델을 너무 깊게 쓰는가?
- API 계약이 안정적인가?
서비스 사이에서:
- 내부 데이터베이스 구조가 밖으로 새는가?
- 배포 순서가 너무 민감한가?
크기는 달라도 같은 결합 질문이 반복된다.
이것이 입문자가 잡아야 할 프랙털 감각이다.
8. 프랙털 모듈성
프랙털 모듈성은 모든 크기에서 균형 잡힌 결합을 만드는 것이다.
작은 함수 안에서도:
static dynamic Total(dynamic order) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
return subtotal(order) + tax(order); // 결과를 호출자에게 돌려줍니다.
}
클래스 안에서도:
public class Order // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
public dynamic Total() // 이 메서드의 입력과 출력이 공개 약속입니다.
{
return this.Subtotal() + this.Tax(); // 결과를 호출자에게 돌려줍니다.
}
}
모듈 사이에서도:
var total = pricing.TotalFor(order); // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
서비스 사이에서도:
{
"event": "OrderPaid",
"amount": 32000
}
각 크기마다 좋은 연결 방식은 다르다.
하지만 원칙은 비슷하다.
- 강한 지식은 가까운 곳에 둔다.
- 먼 연결은 안정적인 계약으로 만든다.
- 자주 바뀌는 것은 흩어지지 않게 한다.
9. 같은 냄새가 다른 크기에서 반복된다
작은 코드의 냄새가 큰 시스템에서도 반복된다.
9.1 플래그 인자
static dynamic SaveUser(dynamic user, dynamic send_email) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
save(user); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
if (send_email) // 조건이 참일 때만 아래 결정을 실행합니다.
{
email(user); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
}
}
호출자가 내부 흐름을 제어한다.
작은 수준의 제어 결합이다.
서비스 수준에서도 비슷한 일이 생긴다.
{
"action": "saveUser",
"sendEmail": true,
"skipValidation": false
}
멀리 있는 호출자가 내부 실행 방식을 제어한다.
규모만 커졌을 뿐 같은 냄새다.
9.2 전체 모델 넘기기
static dynamic RenderName(dynamic user) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
return user.Profile.Name; // 결과를 호출자에게 돌려줍니다.
}
이름 하나만 필요하지만 전체 user를 넘긴다.
이것은 스탬프 결합의 예다.
서비스 사이에서도 전체 사용자 모델을 넘기면 비슷한 문제가 생긴다.
{
"user": {
"id": 1,
"profile": { "name": "김초보" },
"billing": { "card": "..." },
"settings": { "marketing": true }
}
}
필요한 것은 이름뿐인데 너무 많은 구조가 공유된다.
9.3 공유 데이터베이스
여러 서비스가 같은 데이터베이스 테이블을 직접 읽고 쓴다면 어떻게 될까?
서비스는 멀리 떨어져 있는데 같은 내부 저장소를 공유한다.
이것은 큰 규모의 강한 결합이다.
한 서비스의 테이블 변경이 다른 서비스 장애가 될 수 있다.
10. 책임 할당이 핵심이다
결국 설계는 "누가 무엇을 알아야 하는가"를 정하는 일이다.
주문 총액 규칙은 어디에 있어야 할까?
결제 실패 정책은 누가 알아야 할까?
사용자 표시 이름은 누가 만들어야 할까?
배송 가능 지역은 어느 모듈의 책임일까?
책임을 잘못 주면 결합이 이상해진다.
예를 들어 화면이 할인 정책을 알면 화면과 정책이 강하게 묶인다.
결제가 배송 규칙을 알면 서로 먼 책임이 섞인다.
알림이 사용자 전체 모델을 알면 필요 이상의 지식이 새어 나간다.
책임 할당은 결합 균형의 출발점이다.
11. 추상화는 복잡함을 지우는 마법이 아니다
추상화는 세부사항을 감춘다.
var price = pricing.TotalFor(order); // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
이 코드를 읽는 사람은 할인, 세금, 배송비 계산을 모두 몰라도 된다.
하지만 추상화는 공짜가 아니다.
- 이름을 이해해야 한다.
- 입력과 출력을 알아야 한다.
- 어디까지 책임지는지 알아야 한다.
- 너무 많은 것을 감추면 디버깅이 어려워질 수 있다.
그래서 좋은 추상화는 "아무거나 숨기기"가 아니다.
자주 바뀌는 세부사항은 숨기고, 안정적인 의미는 드러내는 것이다.
12. 작은 규모와 큰 규모의 균형표
| 크기 | 가까운 강한 결합 예 | 먼 약한 결합 예 |
|---|---|---|
| 함수 | 내부 helper 호출 | 명확한 입력값 |
| 클래스 | private 메서드와 필드 | public 메서드 |
| 모듈 | 같은 책임의 내부 타입 | 모듈 API |
| 서비스 | 내부 데이터 모델 | 이벤트, REST 계약 |
| 팀 | 같은 팀의 작업 규칙 | 문서화된 인터페이스 |
규모가 커질수록 내부 구조를 직접 공유하면 위험해진다.
반대로 작은 규모에서 너무 멀리 떨어진 계약을 만들면 과해질 수 있다.
균형은 크기에 맞아야 한다.
13. 정리
12장은 결합 균형을 더 큰 눈으로 보게 한다.
소프트웨어는 네트워크다.
부품이 늘면 연결 관리가 핵심 문제가 된다.
기능은 하나씩 늘어도 복잡함은 더 빠르게 늘 수 있다.
사람의 이해력은 거의 고정되어 있으므로, 구조가 성장을 도와야 한다.
프랙털 모듈성은 어려운 말처럼 보이지만 핵심은 단순하다.
함수, 클래스, 모듈, 서비스, 팀 어디서든 결합 균형 질문은 반복된다.
강한 지식은 가까이에.
먼 연결은 약한 계약으로.
자주 바뀌는 것은 흩어지지 않게.
더 해보기
- 지금 보고 있는 프로젝트에서 같은 냄새가 작은 코드와 큰 구조에 반복되는지 찾아보자.
- 전체 모델을 넘기는 함수나 API가 있는지 찾아보자.
- "이 책임은 어디에 있어야 가장 가까운가?"라는 질문으로 한 가지 구조를 다시 봐보자.
연습문제
- 설명.
시스템도 자란다의 핵심을 처음 듣는 사람에게 한 문장으로 설명하라. - 구분. 두 개념(
작은 모듈,큰 시스템)을 실제 예시 하나로 구분하라. - 적용. 내 프로젝트나 학습 노트에서 이 장의 개념을 적용해 작게 개선할 지점을 하나 고르라.
부록 A. 쉬운 용어 사전
| 한글 용어 | 원문 영문명 | 아주 쉬운 뜻 | 이 장에서 나온 위치 |
|---|---|---|---|
| 작은 모듈 | - | 작은 단위에서 책임과 경계를 가진 부품. | 부록 B와 본문 예시 |
| 큰 시스템 | - | 여러 모듈, 팀, 배포 단위가 모여 움직이는 전체 구조. | 부록 B와 본문 예시 |
| 추상화 | Abstraction | 구체적인 속사정보다 필요한 약속과 의미만 남기는 일. | 부록 B와 본문 예시 |
| 책임 | Responsibility Assignment | 그 부품이 왜 존재하고 무엇을 맡는지에 대한 이유. | 부록 B와 본문 예시 |
부록 B. 헷갈리는 개념 비교표
| A | B | 구분 포인트 |
|---|---|---|
| 작은 모듈 | 큰 시스템 | 둘 다 결합 균형 원리가 반복되지만 보는 크기가 다르다. |
| 추상화 | 책임 | 추상화는 표현 방식이고, 책임은 왜 그 부품이 존재하는지다. |
부록 C. 더 읽을 자료
- 이 장의
더 해보기섹션 — 이미 모아 둔 공식 문서나 실습 링크가 있으면 여기서 먼저 확인한다. - 같은 책의
0장 한눈에 보기— 용어가 막히면 0장의 용어집과 개념 척추로 돌아간다. - 원본 딥다이브판 같은 장 — 입문판을 읽고 큰 흐름이 잡힌 뒤 세부 논리를 더 깊게 확인한다.
- 이 장의
flashcards.json— 읽은 직후 질문만 보고 답을 떠올리는 회상 연습에 쓴다.
부록 D. 연습문제 풀이
- 설명 예시.
시스템도 자란다는 변경이 어디로 번지는지 보고, 필요한 연결과 줄여야 할 연결을 구분하게 해 주는 장이다. 중요한 것은 용어를 외우는 것이 아니라, 이 개념이 어떤 입력·부품·결정에 영향을 주는지 말로 풀어 보는 것이다. - 구분 예시. 두 개념(
작은 모듈,큰 시스템)의 차이는 이렇게 잡으면 된다. 둘 다 결합 균형 원리가 반복되지만 보는 크기가 다르다. 실제 사례를 볼 때는 목적, 입력, 실패했을 때의 증상을 따로 적어 보면 헷갈리지 않는다. - 적용 예시. 가장 작은 개선부터 고른다. 예를 들어 이름을 더 분명히 하거나, 평가 기준을 한 줄 추가하거나, 직접 알 필요 없는 내부 정보를 감추는 식으로 시작한다. 한 번에 크게 갈아엎는 것보다 작은 변경 하나를 확인하며 진행하는 쪽이 입문 단계에 맞다.
클릭하거나 Space를 눌러 뒤집기